<!--
/// Copyright (c) 2010 Vrije Universiteit Brussel 
/// 
/// Redistribution and use in source and binary forms, with or without modification, are permitted provided
/// that the following conditions are met: 
///    * Redistributions of source code must retain the above copyright notice, this list of conditions and
///      the following disclaimer. 
///    * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and 
///      the following disclaimer in the documentation and/or other materials provided with the distribution.  
///    * Neither the name of Mozilla Foundation nor the names of its contributors may be used to
///      endorse or promote products derived from this software without specific prior written permission.
/// 
/// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
/// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
/// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
/// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
/// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
/// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
/// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
/// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
-->

<html>
<head>
<script>
function print(msg) {
  document.write(msg+"<br>");
}

/**
 * <tt>probe(obj, log)</tt> returns a probe proxy that will
 * log all meta-level operations by calling <tt>log(record)</tt> and then forward
 * the meta-level operation to obj. The log function is optional and defaults to printing
 * the log record to the console.
 *
 * the record argument to the log function is defined as follows:
 * { op: "operation name",
 *   args: [ table of trap arguments ],
 *   res: undefined | <result from forwarding the operation>
 *   exc: undefined | <exception thrown when forwarding operation> }
 */
function probe(obj, log) {
  log = ensureLog(log);
  return Proxy.create(makeProbeHandler(obj, log), Object.getPrototypeOf(obj));
}

/**
 * Same as <tt>probe</tt> except that this returns a function proxy that
 * additionally logs call and construct operations.
 */
function probef(fun, log) {
  log = ensureLog(log);
  return Proxy.createFunction(makeProbeHandler(fun, log),
    //call
    function(var_args) {
      var args = Array.prototype.slice.call(arguments);
      try {
        var val = fun.apply(this, args);
        log({op: 'call', args: args, res: val });
        return val;
      } catch(e) {
        log({op: 'call', args: args, exc: e });
        throw e;
      }
    },
    //construct
    function(var_args) {
      var args = Array.prototype.slice.call(arguments);
      try {
        var val = fun.apply(Object.create(fun.prototype), args);
        log({op: 'construct', args: args, res: val });
        return val;        
      } catch(e) {
        log({op: 'construct', args: args, exc: e });
        throw e;
      }
    }
  );
};

function ensureLog(log) {
  return log || function(op) {
    print(op.op + '(' + op.args.join(',') + ') = ' +
      (op.res ? op.res : ('exception: ' + op.exc) ));
  };
};
 
function makeProbeHandler(obj, log) {
  return {
    // fundamental traps
    
  	getOwnPropertyDescriptor: function(name) {
  	  try {
    	  var desc = Object.getOwnPropertyDescriptor(obj, name);
    	  log({op: 'getOwnPropertyDescriptor', args: [name], res: desc });
    	  return desc;
  	  } catch (e) {
  	    log({op: 'getOwnPropertyDescriptor', args: [name], exc: e });
  	    throw e;
  	  }
  	},
  	getPropertyDescriptor: function(name) { // ES-harmony addition
  	  try {
  	    var desc = Object.getPropertyDescriptor(obj, name); // assumed
    	  // a trapping proxy's properties must always be configurable
    	  // desc.configurable = true;
    	  log({op: 'getPropertyDescriptor', args: [name], res: desc });
    	  return desc;
  	  } catch (e) {
    	  log({op: 'getPropertyDescriptor', args: [name], exc: e });
  	    throw e;
  	  }
  	},
  	getOwnPropertyNames: function() {
  	  try {
  	    var names = Object.getOwnPropertyNames(obj);
    	  log({op: 'getOwnPropertyNames', args: [], res: names });
    	  return names;
  	  } catch (e) {
  	    log({op: 'getOwnPropertyNames', args: [], exc: e });
  	    throw e;
  	  }
  	},
  	getPropertyNames: function() { // ES-harmony addition
  	  try {
  	    var names = Object.getPropertyNames(obj);
    	  log({op: 'getPropertyNames', args: [], res: names });
    	  return names;
  	  } catch (e) {
  	    log({op: 'getPropertyNames', args: [], exc: e });
  	    throw e;
  	  }
  	},
  	defineProperty: function(name, desc) {
  	  try {
  	    var val = Object.defineProperty(obj, name, desc);
  	    log({op: 'defineProperty', args: [name,desc], res: val });
    	  return val;
  	  } catch (e) {
  	    log({op: 'defineProperty', args: [name,desc], exc: e });
  	    throw e;
  	  }
  	},
  	'delete': function(name) {
  	  try {
  	    var bool = delete obj[name];
  	    log({op: 'delete', args: [name], res: bool });
    	  return bool;
  	  } catch (e) {
  	    print(uneval(e));
  	    log({op: 'delete', args: [name], exc: e });
  	    throw e;
  	  }
  	},
  	fix: function() {
  	  try {
  	    var props = {};
    	  for (x in obj) {
    		  props[x] = Object.getOwnPropertyDescriptor(obj, x);
    		}
    		Object.freeze(obj);
    		log({op: 'fix', args: [], res: props });
    	  return props;
  	  } catch (e) {
  	    log({op: 'fix', args: [], exc: e });
  	    throw e;
  	  }
  	},
  	
  	// derived traps
  	
   	has: function(name) {
   	  try {
  	    var bool = name in obj;
  	    log({op: 'has', args: [name], res: bool });
     	  return bool;
  	  } catch (e) {
  	    log({op: 'has', args: [name], exc: e });
  	    throw e;
  	  }
   	},
  	hasOwn: function(name) {
  	  try {
  	    var bool = ({}).hasOwnProperty.call(obj, name);
  	    log({op: 'hasOwn', args: [name], res: bool });
    	  return bool;
  	  } catch (e) {
  	    log({op: 'hasOwn', args: [name], exc: e });
  	    throw e;
  	  }
  	},
  	get: function(receiver, name) {
  	  try {
    	  var val = obj[name];
    	  log({op: 'get', args: [obj,name], res: val });
    	  return val;
  	  } catch (e) {
  	    log({op: 'get', args: [obj,name], exc: e });
  	    throw e;
  	  }
  	},
  	set: function(receiver, name, val) {
  	  try {
  	    obj[name] = val;
  	    var success = (obj[name] === val);
  	    log({op: 'set', args: [obj,name,val], res: success });
  	    return success;
  	  } catch (e) {
  	    log({op: 'set', args: [obj,name,val], exc: e });
  	    throw e;
  	  }
  	}, 
  	enumerate: function() {
  	  try {
    	  var result = [];
    	  for (name in obj) { result.push(name); };
    	  log({op: 'enumerate', args: [], res: result });
    	  return result;  	    
  	  } catch (e) {
  	    log({op: 'enumerate', args: [], exc: e });
  	    throw e;
  	  }
  	},
  	keys: function() {
  	  try {
  	    var ks = Object.keys(obj);
  	    log({op: 'keys', args: [], res: ks });
    	  return ks;
  	  } catch (e) {
  	    log({op: 'keys', args: [], exc: e });
  	    throw e;
  	  }
  	}
  };
};

function testObjectOps(p) {
  print('-- calling getOwnPropertyDescriptor');
  Object.getOwnPropertyDescriptor(p, 'foo');
  // print('-- calling getPropertyDescriptor');
  // FIXME: Object.getPropertyDescriptor not implemented
  // Object.getPropertyDescriptor(p,'foo');
  print('-- calling getOwnPropertyNames');
  Object.getOwnPropertyNames(p);
  print('-- calling defineProperty');
  Object.defineProperty(p, 'baz', { value: 44, writable: true });
  print('-- calling delete');
  delete p['bar'];
  print('-- calling has');
  'foo' in p;
  print('-- calling hasOwn');
  Object.prototype.hasOwnProperty.call(p, 'foo');
  print('-- calling get');
  p.foo;
  print('-- calling set');
  p.foo = 24;
  print('-- calling enumerate');
  for (var prop in p) { };
  print('-- calling keys');
  Object.keys(p);
  print('-- calling freeze');
  if (Object.freeze) { Object.freeze(p); } else { print('skipping Object.freeze') };
  print('-- done'); 
};

function testFunOps(p) {
  print('-- calling call');
  p(1,2,3);
  print('-- calling construct');
  new p(1,2,3);
  print('-- done');
}

function run() {
  testObjectOps(probe({ 'foo':42, bar: 24 }));
  testFunOps(probef(function(a,b,c) { return a; })); 
}
</script>
</head>
<body onload="run()">
</body>
</html>